// Fill out your copyright notice in the Description page of Project Settings.


#include "System/MotionSubtitlesSubsystem.h"
#include "MotionSubtitlesActor.h"
#include "Layers/MotionSubtitlesTextLayer.h"

UMotionSubtitlesSubsystem* UMotionSubtitlesSubsystem::Get()
{
	return GWorld->GetSubsystem<UMotionSubtitlesSubsystem>();
}

bool UMotionSubtitlesSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
	return Super::ShouldCreateSubsystem(Outer);
}

void UMotionSubtitlesSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);
}

void UMotionSubtitlesSubsystem::Deinitialize()
{
	Super::Deinitialize();

	// 销毁所有字幕演员
	for (auto Pairs : Mappings)
	{
		if (Pairs.Value != nullptr && IsValid(Pairs.Value.Get()))
		{
			Pairs.Value->Destroy();
		}
	}
	Mappings.Empty();
}

bool UMotionSubtitlesSubsystem::Create(const UObject* WorldContextObject, const FString& InFolderPath, FGuid& Id)
{
	TArray<FString> Files;
	if (!GetAllFiles(InFolderPath, Files))
	{
		return false;
	}

	if (Files.Num() <= 0)
	{
		UE_LOG(LogTemp, Error, TEXT("%s 未找到任何uasset资源"), *InFolderPath)
		return false;
	}

	AActor* Actor = nullptr;
	for (auto File : Files)
	{
		FString AssetName;
		if (!GetAssetName(File, AssetName))
		{
			continue;
		}

		if (CreateSubtitlesActor(WorldContextObject, AssetName, Actor))
		{
			UE_LOG(LogTemp, Log, TEXT("%s 特效资源加载成功"), *InFolderPath)
			break;
		}
	}

	return Register(Actor, Id);
}

bool UMotionSubtitlesSubsystem::Show(const UObject* WorldContextObject, const FGuid& Id, const FSubtitleParams& Params,
                                     const TFunction<void()>& OnFinished)
{
	if (!IsValidSubtitles(Id))
	{
		OnFinished();
		return false;
	}

	const TWeakObjectPtr<AActor> Actor = Mappings[Id];
	if (Actor == nullptr)
	{
		OnFinished();
		return false;
	}

	AMotionSubtitlesActor* SubtitlesActor = Cast<AMotionSubtitlesActor>(Actor.Get());
	if (SubtitlesActor == nullptr)
	{
		OnFinished();
		return false;
	}
	SubtitlesActor->ShowText(Params);

	// 定时
	FTimerHandle InOutHandle;
	TSharedPtr<FTimerHandle> HandlePtr = MakeShared<FTimerHandle>(InOutHandle);
	UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
	if (World == nullptr)
	{
		OnFinished();
		return false;
	}

	const float Duration = Params.Duration;
	World->GetTimerManager().SetTimer(InOutHandle, [this,World,SubtitlesActor,HandlePtr,OnFinished]()-> void
	{
		if (IsValid(SubtitlesActor))
		{
			SubtitlesActor->HideText();
		}

		World->GetTimerManager().ClearTimer(*HandlePtr.Get());

		OnFinished();
	}, Duration, false);

	return true;
}

bool UMotionSubtitlesSubsystem::ShowOnce(const UObject* WorldContextObject, const FString& InFolderPath,
                                         const FSubtitleParams& Params,
                                         const TFunction<void()>& OnFinished)
{
	FGuid Id;
	if (!Create(WorldContextObject, InFolderPath, Id))
	{
		OnFinished();
		return false;
	}

	return Show(WorldContextObject, Id, Params, [this,Id,OnFinished]()-> void
	{
		OnFinished();
		Destroy(Id);
	});
}

bool UMotionSubtitlesSubsystem::Destroy(const FGuid Id)
{
	AActor* Actor = nullptr;
	if (!UnRegister(Id, Actor))
	{
		return false;
	}

	if (IsValid(Actor))
	{
		Actor->Destroy();
	}
	return true;
}

bool UMotionSubtitlesSubsystem::Register(AActor* Actor, FGuid& Id)
{
	if (Canvas == nullptr)
	{
		AActor* Obj = GetWorld()->SpawnActor(AMotionSubtitlesCanvas::StaticClass());
		Canvas = MakeWeakObjectPtr(Cast<AMotionSubtitlesCanvas>(Obj));
	}

	if (IsValidSubtitles(Actor))
	{
		Id = FGuid::NewGuid();
		Mappings.Add(Id, MakeWeakObjectPtr(Actor));

		// 添加到Canvas
		const AMotionSubtitlesActor* SubtitlesActor = Cast<AMotionSubtitlesActor>(Actor);
		Canvas->Add(Id, SubtitlesActor->GetPostProcessMaterial());

		return true;
	}
	return false;
}

bool UMotionSubtitlesSubsystem::UnRegister(const FGuid Id, AActor*& Actor)
{
	if (!IsValidSubtitles(Id))
	{
		return false;
	}

	Actor = Mappings[Id].Get();
	Mappings.Remove(Id);

	// 从Canvas移除
	if (Canvas != nullptr)
	{
		Canvas->Remove(Id);
	}

	return true;
}

bool UMotionSubtitlesSubsystem::GetAllFiles(const FString& InFolderPath, TArray<FString>& Files)
{
	IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();

	const FString GameDir = FPaths::ProjectDir() + "Content/";
	const FString AbsolutePath = InFolderPath.Replace(L"/Game/", *GameDir);
	if (!PlatformFile.DirectoryExists(*AbsolutePath))
	{
		return false;
	}

	PlatformFile.FindFilesRecursively(Files, *AbsolutePath, TEXT(".uasset"));
	return true;
}

bool UMotionSubtitlesSubsystem::GetAssetName(const FString& FilePath, FString& AssetName)
{
	if (FilePath.IsEmpty())
	{
		return false;
	}

	const FString BaseFilename = FPaths::GetBaseFilename(FilePath);

	FString FileName = FilePath;
	FileName.RemoveFromEnd(TEXT(".uasset"));
	const int32 Pos = FileName.Find("/Content/");
	FileName = FileName.RightChop(Pos + 8);
	FileName = FString::Printf(TEXT("Blueprint'/Game%s.%s_C'"), *FileName, *BaseFilename);
	AssetName = FileName;

	return true;
}

bool UMotionSubtitlesSubsystem::CreateSubtitlesActor(const UObject* WorldContextObject, const FString& AssetName,
                                                     AActor*& SubtitlesActor)
{
	UClass* ActorType = StaticLoadClass(AActor::StaticClass(), nullptr, *AssetName);
	if (ActorType == nullptr)
	{
		return false;
	}

	if (!ActorType->IsChildOf(AMotionSubtitlesActor::StaticClass()))
	{
		return false;
	}

	UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
	if (World == nullptr)
	{
		return false;
	}

	const FVector Location = FVector(0, 0, -2000);
	SubtitlesActor = World->SpawnActor<AActor>(ActorType, Location, FRotator::ZeroRotator);
	if (IsValid(SubtitlesActor))
	{
		return true;
	}
	return false;
}

bool UMotionSubtitlesSubsystem::IsValidSubtitles(const FGuid& Id)
{
	if (!Mappings.Contains(Id))
	{
		return false;
	}

	const TWeakObjectPtr<AActor> TextActor = Mappings[Id];
	if (!IsValid(TextActor.Get()))
	{
		return false;
	}

	const AMotionSubtitlesActor* Actor = Cast<AMotionSubtitlesActor>(Mappings[Id].Get());
	if (Actor == nullptr)
	{
		return false;
	}
	return true;
}

bool UMotionSubtitlesSubsystem::IsValidSubtitles(const AActor* Actor)
{
	if (!IsValid(Actor))
	{
		return false;
	}

	const AMotionSubtitlesActor* SubtitlesActor = Cast<AMotionSubtitlesActor>(Actor);
	if (SubtitlesActor == nullptr)
	{
		return false;
	}
	return true;
}
